home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Cream of the Crop 3
/
Cream of the Crop 3.iso
/
comm
/
wnos5src.zip
/
FTPCLI.C
< prev
next >
Wrap
Text File
|
1993-10-19
|
20KB
|
901 lines
/* FTP client (interactive user) code */
#include <stdio.h>
#include <ctype.h>
#include <io.h>
#include <sys/stat.h>
#include "global.h"
#include "mbuf.h"
#include "session.h"
#include "cmdparse.h"
#include "proc.h"
#include "tty.h"
#include "socket.h"
#include "ftp.h"
#include "commands.h"
#include "netuser.h"
#include "dirutil.h"
#include "files.h"
#include "clients.h"
static int dotype __ARGS((int argc,char **argv,void *p));
/* Wait for, read and display response from FTP server. Return the result code.
*/
static int near
getresp(struct ftpcli *ftp,int mincode)
{
int rval = 0;
char buf[FTPLINE];
usflush(ftp->control);
for(;;){
/* Get line */
if(recvline(ftp->control,buf,FTPLINE) == -1) {
/* he closed on us */
ftp->state = CLOSED;
return -1;
}
if((rval = atoi(buf)) >= 400 || ftp->verbose >= V_NORMAL) {
/* Display to user */
tputs(buf);
}
/* Messages with dashes are continued */
if(buf[3] != '-' && rval >= mincode)
return rval;
}
}
/* Issue a prompt and read a line from the user */
static int near
getline(int s,char *prompt,char *buf,int n)
{
/* If there's something already there, don't issue prompt */
if(socklen(s,0) == 0) {
tputs(prompt);
}
return recvline(s,buf,n);
}
static int near
prepconnection(struct ftpcli *ftp,char *command,char *remote,long offset)
{
int resp, typewait = 0;
struct sockaddr_in lsocket, lcsocket;
if(ftp->typesent != ftp->type) {
char *typestr;
switch(ftp->type){
case LOGICAL_TYPE:
typestr = "TYPE L %d\n";
break;
case ASCII_TYPE:
typestr = "TYPE A\n";
break;
case IMAGE_TYPE:
typestr = "TYPE I\n";
break;
}
usprintf(ftp->control,typestr,ftp->logbsize);
ftp->typesent = ftp->type;
if(!ftp->batch){
resp = getresp(ftp,200);
if(resp == -1 || resp > 299) {
return -1;
}
} else {
typewait = 1;
}
}
/* Send the PORT message. Use the IP address
* on the local end of our control connection
*/
resp = SOCKSIZE;
getsockname(ftp->data,(char *)&lsocket,&resp);
resp = SOCKSIZE;
getsockname(ftp->control,(char *)&lcsocket,&resp);
lsocket.sin_addr.s_addr = lcsocket.sin_addr.s_addr;
/* Send PORT a,a,a,a,p,p message */
usprintf(ftp->control,"PORT %u,%u,%u,%u,%u,%u\n",
hibyte(hiword(lsocket.sin_addr.s_addr)),
lobyte(hiword(lsocket.sin_addr.s_addr)),
hibyte(loword(lsocket.sin_addr.s_addr)),
lobyte(loword(lsocket.sin_addr.s_addr)),
hibyte(lsocket.sin_port),
lobyte(lsocket.sin_port));
if(!ftp->batch){
/* Get response to PORT command */
resp = getresp(ftp,200);
if(resp == -1 || resp > 299){
return -1;
}
}
/* Generate the command to start the transfer */
usprintf(ftp->control,offset ? "%s %s %ld\n" : "%s %s\n",
command,
remote ? remote : "",
offset);
if(ftp->batch){
/* Get response to TYPE command, if sent */
if(typewait){
resp = getresp(ftp,200);
if(resp == -1 || resp > 299){
return -1;
}
}
/* Get response to PORT command */
resp = getresp(ftp,200);
if(resp == -1 || resp > 299){
return -1;
}
}
/* Get the intermediate "150" response */
resp = getresp(ftp,100);
if(resp == -1 || resp >= 400){
return -1;
}
return 0;
}
/* bke 920815 */
static char * near
gen_fname(char *s)
{
char *cp;
static char du[MAXPATH];
while((cp = strchr(s,'\\')) != NULLCHAR) { /* change backslash */
*cp = '/';
}
if(strchr(s,'/')) {
return s; /* path included? */
}
if((cp = getenv("FTPDIR")) == NULL) {
cp = Ftpdir;
}
if(access(cp,0)) {
return s; /* path does not exists... */
}
sprintf(du,"%s/%s",cp,s);
return du;
}
/* bke */
/* Common code to LIST/NLST/RETR and mget
* Returns number of bytes received if successful
* Returns -1 on error
*/
static long near
getsub(struct ftpcli *ftp,char *command,char *remote,char *local,long offset)
{
unsigned long total = -1;
FILE *fp;
int cnt = 0;
int32 startclk, rate;
int prevstate = ftp->state;
int vsave = ftp->verbose;
int savmode = ftp->type;
char *mode = (ftp->type == ASCII_TYPE) ? WRITE_TEXT : WRITE_BINARY;
if(offset) {
mode = "rb+";
}
/* Open the file */
if(local == NULLCHAR) {
fp = NULLFILE;
} else if((fp = Fopen(gen_fname(local),mode,0,1)) == NULLFILE) {
return -1;
}
if(fp) {
if(fseek(fp,offset,SEEK_SET)) {
tprintf("Can't position %s: %s\n",local,sys_errlist[errno]);
Fclose(fp);
return -1;
}
}
/* Open the data connection */
ftp->data = socket(AF_INET,SOCK_STREAM,0);
/* Accept only one connection */
listen(ftp->data,0);
ftp->state = RECEIVING_STATE;
/* Send TYPE message, if necessary */
if(strcmp(command,"LIST") == 0 || strcmp(command,"NLST") == 0) {
/* Directory listings are always in ASCII */
ftp->type = ASCII_TYPE;
}
if(prepconnection(ftp,command,remote,offset) == -1) {
goto failure;
}
/* Wait for the server to open the data connection */
ftp->data = accept(ftp->data,NULLCHAR,&cnt);
startclk = msclock();
if(vsave >= V_HASH && fp == NULLFILE)
ftp->verbose = V_NORMAL;
total = recvfile(fp,ftp->data,ftp->type,ftp->verbose >= V_HASH ? ftp->verbose : 0);
/* Immediately close the data connection; some servers (e.g., TOPS-10)
* wait for the data connection to close completely before returning
* the completion message on the control channel
*/
if(fp != NULLFILE)
Fclose(fp);
close_s(ftp->data);
ftp->data = -1;
if(remote == NULLCHAR)
remote = "";
if(total == -1)
tprintf("%s %s: Error/abort during data transfer\n",command,remote);
/* Get the "Sent" message */
if(getresp(ftp,200) == -1) {
total = -1;
}
if(total != -1 && ftp->verbose >= V_SHORT) {
startclk = msclock() - startclk;
rate = (startclk != 0) ? (total*1000) / startclk : 0;
tprintf("%s %s: %lu bytes in %lu sec (%lu/sec)\n",
command,remote,total,startclk/1000,rate);
}
goto quit;
failure:
/* Error, quit */
if(fp != NULLFILE)
Fclose(fp);
close_s(ftp->data);
ftp->data = -1;
quit:
ftp->verbose = vsave;
ftp->state = prevstate;
ftp->type = savmode;
return total;
}
/* Common code to put, mput */
static long near
putsub(struct ftpcli *ftp,char *remote,char *local)
{
unsigned long total;
FILE *fp;
int32 startclk, rate;
int prevstate = ftp->state;
char *mode = (ftp->type == ASCII_TYPE) ? READ_TEXT : READ_BINARY;
/* Open the file */
if((fp = Fopen(local,mode,0,0)) == NULLFILE) {
return -1;
}
if(ftp->type == ASCII_TYPE && isbinary(fp)) {
tprintf("Warning: type is ASCII and %s appears to be binary\n",local);
}
/* Open the data connection */
ftp->data = socket(AF_INET,SOCK_STREAM,0);
/* Accept only one connection */
listen(ftp->data,0);
ftp->state = SENDING_STATE;
if(prepconnection(ftp,"STOR",remote,0) == -1) {
goto failure;
}
/* Wait for the data connection to open. Otherwise the first
* block of data would go out with the SYN, and this may confuse
* some other TCPs
*/
accept(ftp->data,NULLCHAR,0);
startclk = msclock();
total = sendfile(fp,ftp->data,ftp->type,(ftp->verbose >= V_HASH ? ftp->verbose : 0) | 0x80);
close_s(ftp->data);
ftp->data = -1;
if(total == -1) {
tprintf("STOR %s: Error/abort during data transfer\n",remote);
}
if(getresp(ftp,200) == -1) {
total = -1;
}
if(total != -1 && ftp->verbose >= V_SHORT) {
startclk = msclock() - startclk;
rate = (startclk != 0) ? (total*1000)/startclk : 0;
tprintf("STOR %s: %lu bytes in %lu sec (%lu/sec)\n",
remote,total,startclk/1000,rate);
}
ftp->state = prevstate;
return total;
failure:
/* Error, quit */
Fclose(fp);
close_s(ftp->data);
ftp->data = -1;
ftp->state = prevstate;
return -1;
}
/* ------------------------- FTP-Client Subcmds ------------------------- */
/* Abort a GET or PUT operation in progress.
* Note: this will leave the partial file on the local or remote system
* This function is called from config.h
*/
int
doabort(int argc,char **argv,void *p)
{
struct session *sp = (struct session *)p;
/* Default is the current session, but it can be overridden with
* an argument.
*/
if(argc > 1) {
sp = sessptr(argv[1]);
}
if(sp != NULLSESSION && sp->type == FTP) {
struct ftpcli *ftp = sp->cb.ftp;
switch(ftp->state){
case COMMAND_STATE:
tputs("No active transfer\n");
return 1;
/* Send a premature EOF.
* Unfortunately we can't just reset the connection
* since the remote side might end up waiting forever
* for us to send something.
* If receiving state just blow away the socket
*/
case SENDING_STATE: /* defined as 1 */
case RECEIVING_STATE: /* defined as 2 */
shutdown(ftp->data,ftp->state);
ftp->abort = 1;
return 0;
}
}
return 1;
}
static int
doascii(int argc,char **argv,void *p)
{
char *args[2];
args[1] = "A";
return dotype(2,args,p);
}
/* Enable/disable command batching */
static int
dobatch(int argc,char **argv,void *p)
{
struct ftpcli *ftp = p;
return setbool(&ftp->batch,"FTP batch",argc,argv);
}
static int
dobinary(int argc,char **argv,void *p)
{
char *args[2];
args[1] = "I";
return dotype(2,args,p);
}
/* Translate 'cd' to 'cwd' for convenience */
static int
doftpcd(int argc,char **argv,void *p)
{
struct ftpcli *ftp = p;
usprintf(ftp->control,"CWD %s\n",argv[1]);
getresp(ftp,200);
return 0;
}
/* Start receive transfer. Syntax: get <remote name> [<local name>] */
static int
doget(int argc,char **argv,void *p)
{
struct ftpcli *ftp = p;
getsub(ftp,"RETR",argv[1],(argc < 3) ? argv[1] : argv[2],0);
return 0;
}
/* Set verbosity to high (convenience command) */
static int
dohash(int argc,char **argv,void *p)
{
struct ftpcli *ftp = p;
ftp->verbose = V_HASH;
return 0;
}
/* List remote directory. Syntax: dir <remote files> [<local name>] */
static int
dolist(int argc,char **argv,void *p)
{
struct ftpcli *ftp = p;
getsub(ftp,"LIST",argv[1],(argc < 3) ? NULLCHAR : argv[2],0);
return 0;
}
/* Remote directory list, short form. Syntax: ls <remote files> [<local name>] */
static int
dols(int argc,char **argv,void *p)
{
struct ftpcli *ftp = p;
getsub(ftp,"NLST",argv[1],(argc < 3) ? NULLCHAR : argv[2],0);
return 0;
}
/* Get a collection of files */
static int
domget(int argc,char **argv,void *p)
{
struct ftpcli *ftp = p;
FILE *fp;
char *local, *c, tmpname[MAXPATH + 1];
int i, inlist;
int32 r;
ftp->state = RECEIVING_STATE;
for(i = 1; i < argc; i++) {
if(ftp->abort) {
break;
}
if(argv[i][0] == '@') {
FILE *filel;
inlist = 1;
if((filel = Fopen(&argv[i][1],"r",ftp->control,0)) == NULLFILE) {
continue;
}
if((fp = Tmpfile(ftp->control,0)) == NULLFILE) {
Fclose(filel);
continue;
}
while(fgets(ftp->buf,FTPLINE,filel) != NULL) {
fputs(ftp->buf,fp);
}
Fclose(filel);
rewind(fp);
} else {
strcpy(tmpname,__tmpnam(NULL,0));
inlist = 0;
if((r = getsub(ftp,"NLST",argv[i],tmpname,0)) == -1) {
continue;
}
if(ftp->abort) {
break;
}
if((fp = Fopen(tmpname,"r",0,1)) == NULLFILE) {
continue;
}
}
/* The tmp file now contains a list of the remote files.
* If any cannot be read, it must be because we were aborted
* or reset locally, so break out if a transfer fails.
*/
while(fgets(ftp->buf,FTPLINE,fp) != NULL) {
rip(ftp->buf);
local = strxdup(ftp->buf);
if(inlist){
strrev(local);
strtok(local, "\\/[]<>,?#~()&%");
strrev(local);
}
if((c = strchr(local,'.')) != NULLCHAR) {
c++;
c = strtok(c,"."); /* remove 2nd period if any */
}
r = getsub(ftp,"RETR",ftp->buf,local,0);
xfree(local);
if(r == -1 || ftp->abort) {
goto quit;
}
}
if(!inlist) {
unlink(tmpname);
}
}
quit:
Fclose(fp);
ftp->abort = 0;
ftp->state = COMMAND_STATE;
return 0;
}
/* Translate 'mkdir' to 'xmkd' for convenience */
static int
domkdir(int argc,char **argv,void *p)
{
struct ftpcli *ftp = p;
usprintf(ftp->control,"XMKD %s\n",argv[1]);
getresp(ftp,200);
return 0;
}
/* Put a collection of files */
static int
domput(int argc,char **argv,void *p)
{
FILE *files;
int i;
struct ftpcli *ftp = p;
if((files = Tmpfile(0,1)) == NULLFILE) {
return 1;
}
for(i = 1; i < argc; i++) {
getdir(argv[i],0,files);
}
rewind(files);
ftp->state = SENDING_STATE;
while(fgets(ftp->buf,FTPLINE,files) != NULL) {
rip(ftp->buf);
if(ftp->abort || putsub(ftp,ftp->buf,ftp->buf) == -1) {
break;
}
}
Fclose(files);
ftp->abort = 0;
ftp->state = COMMAND_STATE;
return 0;
}
/* NO-OP function */
static int
donothing(int argc,char **argv,void *p)
{
return 0;
}
/* Send a file. Syntax: put <local name> [<remote name>] */
static int
doput(int argc,char **argv,void *p)
{
struct ftpcli *ftp = p;
putsub(ftp,(argc < 3) ? argv[1] : argv[2],argv[1]);
return 0;
}
/* Close session */
static int
doquit(int argc,char **argv,void *p)
{
struct ftpcli *ftp = p;
usputs(ftp->control,"QUIT\n");
getresp(ftp,200); /* Get the closing message */
getresp(ftp,200); /* Wait for the server to close */
ftp->state = CLOSED;
return 0;
}
/* Translate 'rmdir' to 'xrmd' for convenience */
static int
dormdir(int argc,char **argv,void *p)
{
struct ftpcli *ftp = p;
usprintf(ftp->control,"XRMD %s\n",argv[1]);
getresp(ftp,200);
return 0;
}
/* Start receive transfer restart.
* Syntax: restart <remote name> [<local name>]
*/
static int
dosrest(int argc,char **argv,void *p)
{
struct ftpcli *ftp = p;
long offset;
struct stat st;
if(stat(gen_fname(argv[1]),&st)) {
tprintf("Can't find %s\n",argv[1]);
return 1;
}
/*-------------------------------------------------------------------*
* adjust offset to latest 1K Boundary
*--------------------------------------------------------------------*/
offset = st.st_size & 0xfffffc00L;
/*-------------------------------------------------------------------*
* let 'em swing
*--------------------------------------------------------------------*/
getsub(ftp,"SREST",(argc < 3) ? argv[1] : argv[2],argv[1],offset);
return 0;
}
/* Handle "type" command from user */
static int
dotype(int argc,char **argv,void *p)
{
struct ftpcli *ftp = p;
if(argc < 2){
switch(ftp->type){
case IMAGE_TYPE:
case ASCII_TYPE:
tprintf("%s\n",(ftp->type == ASCII_TYPE) ? "Ascii" : "Image");
break;
case LOGICAL_TYPE:
tprintf("Logical bytesize %u\n",ftp->logbsize);
break;
}
return 0;
}
switch(tolower(*argv[1])) {
case 'i':
case 'b':
ftp->typesent = ftp->type = IMAGE_TYPE;
usputs(ftp->control,"TYPE I\n");
break;
case 'a':
ftp->typesent = ftp->type = ASCII_TYPE;
usputs(ftp->control,"TYPE A\n");
break;
case 'l':
ftp->typesent = ftp->type = LOGICAL_TYPE;
ftp->logbsize = atoi(argv[2]);
usprintf(ftp->control,"TYPE L %s\n",argv[2]);
break;
default:
tprintf("Invalid type %s\n",argv[1]);
return 0;
}
getresp(ftp,200);
return 0;
}
/* Control verbosity level */
static int
doverbose(int argc,char **argv,void *p)
{
struct ftpcli *ftp = p;
return setintrc(&ftp->verbose,"Verbose",argc,argv,0,4);
}
/* Handle top-level FTP command */
int
doftp(int argc,char **argv,void *p)
{
struct ftpcli *ftp;
struct session *sp;
struct sockaddr_in fsocket;
int resp, vsave;
char *cp = NULLCHAR;
static struct cmds Ftpcmds[] = {
"", donothing, 0, 0, NULLCHAR,
"ascii", doascii, 0, 0, NULLCHAR,
"batch", dobatch, 0, 0, NULLCHAR,
"binary", dobinary, 0, 0, NULLCHAR,
"cd", doftpcd, 0, 2, "cd <dir>",
"dir", dolist, 0, 0, NULLCHAR,
"get", doget, 0, 2, "get <remotefile> <localfile>",
"hash", dohash, 0, 0, NULLCHAR,
"list", dolist, 0, 0, NULLCHAR,
"ls", dols, 0, 0, NULLCHAR,
"mget", domget, 0, 2, "mget <file> [<file> ...]",
"mkdir", domkdir, 0, 2, "mkdir <dir>",
"mput", domput, 0, 2, "mput <file> [<file> ...]",
"nlst", dols, 0, 0, NULLCHAR,
"put", doput, 0, 2, "put <localfile> <remotefile>",
"quit", doquit, 0, 0, NULLCHAR,
"restart", dosrest, 0, 2, "restart <remotefile> <localfile>",
"rmdir", dormdir, 0, 2, "rmdir <dir>",
"type", dotype, 0, 0, NULLCHAR,
"verbose", doverbose, 0, 0, NULLCHAR,
NULLCHAR, NULLFP, 0, 0, NULLCHAR,
};
/* Allocate a session control block */
if((sp = newsession(argv[1],FTP,SWAP)) == NULLSESSION) {
tputs(Nosess);
return -1;
}
ftp = mxallocw(sizeof(struct ftpcli));
ftp->session = sp;
ftp->verbose = V_NORMAL;
ftp->control = ftp->data = -1;
sp->cb.ftp = ftp; /* Downward link */
fsocket.sin_family = AF_INET;
fsocket.sin_port = (argc < 3) ? IPPORT_FTP : atoi(argv[2]);
tprintf("Resolving %s... ",sp->name);
if((fsocket.sin_addr.s_addr = resolve(sp->name)) == 0){
tprintf(Badhost,sp->name);
goto quit2;
}
/* Open the control connection */
if((sp->s = socket(AF_INET,SOCK_STREAM,0)) == -1){
tputs(Nosocket);
goto quit2;
}
sockmode(sp->s,SOCK_ASCII);
ftp->control = sp->s;
/* Buffer to process responses and commands */
ftp->buf = mxallocw(FTPLINE);
tprintf("Trying %s...\n",psocket((struct sockaddr *)&fsocket));
if(connect(ftp->control,(char *)&fsocket,SOCKSIZE) == -1) {
goto quit;
}
tprintf("FTP session connected to %s\n",sp->name);
log(ftp->control,9983,"FTP connect");
/* Wait for greeting from server */
if((resp = getresp(ftp,200)) >= 400) {
goto quit;
}
for( ; ;) {
if(ftp->state == CLOSED) {
break;
}
switch(resp) {
default:
if(getline(sp->input,"ftp> ",ftp->buf,FTPLINE) == -1) {
ftp->state = CLOSED;
goto quit;
}
cp = strxdup(ftp->buf);
if((resp = cmdparse(Ftpcmds,ftp->buf,ftp)) == -1) {
if(ftp->state == CLOSED) {
xfree(cp);
goto quit;
}
/* Not a local cmd, send to remote server */
usputs(ftp->control,cp);
/* Enable display of server response */
vsave = ftp->verbose;
ftp->verbose = V_NORMAL;
if((resp = getresp(ftp,200)) == -1) {
xfree(cp);
goto quit;
}
ftp->verbose = vsave;
}
xfree(cp);
break;
case 220:
/* Sign-on banner; prompt for and send USER command */
strcpy(ftp->buf,sp->name);
if(!cli_login(IPPORT_FTP,(void *)ftp)) {
if(getline(sp->input,"Enter user name: ",ftp->buf,FTPLINE) == -1) {
goto quit;
}
rip(ftp->buf);
ftp->username = strxdup(ftp->buf);
if(ftp->password) {
xfree(ftp->password);
ftp->password = NULLCHAR;
}
}
usprintf(ftp->control,"USER %s\n",ftp->username);
if((resp = getresp(ftp,200)) == -1) {
goto quit;
}
break;
case 331:
/* Password prompt; get password */
if(ftp->password == NULLCHAR) {
/* turn off echo */
sp->ttystate.echo = 0;
if(getline(sp->input,"Password: ",ftp->buf,FTPLINE) == -1) {
goto quit;
}
sp->ttystate.echo = 1;
tputs("\n");
rip(ftp->buf);
ftp->password = strxdup(ftp->buf);
}
usprintf(ftp->control,"PASS %s\n",ftp->password);
xfree(ftp->password);
ftp->password = NULLCHAR;
if((resp = getresp(ftp,200)) == -1) {
goto quit;
}
break;
}
}
quit:
if((cp = sockerr(ftp->control)) == NULLCHAR) {
cp = "EOF";
}
tprintf("FTP session closed: %s\n",cp);
log(ftp->control,9983,"FTP closed %s",cp);
if(ftp->fp != NULLFILE) {
Fclose(ftp->fp);
}
if(ftp->data != -1) {
close_s(ftp->data);
}
if(ftp->control != -1) {
close_s(ftp->control);
}
keywait(NULLCHAR,1);
freesession(ftp->session);
if(ftp->username != NULLCHAR) {
xfree(ftp->username);
}
xfree(ftp->buf);
xfree(ftp);
return 0;
quit2:
keywait(NULLCHAR,1);
freesession(ftp->session);
xfree(ftp);
return -1;
}